﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSE.AuditLogging.LogEntry;
using System.IO;
using PSE.Framework.Serialization;
using System.Threading;
using PSE.Framework.ProviderLoader;
using PSE.Framework.ProviderLoader.Configuration;
using System.Configuration;
using PSE.AuditLogging.Common.Exception;
using System.Runtime.Serialization;

namespace PSE.AuditLogging.Repository.Providers
{
    public class FileSystemRepository : BaseProvider, IContingencyRepository
    {
        //private Mutex _mtx = new Mutex(true, "PSE.AuditLogging.Repository.FileSystemRepository");
        private object _syncRoot = new object();
        private string _fileNamePattern = "yyyyMMddHHmmssffff";
        private string _fileExtension = ".bin";
        private string _contingencyDirectoryPath;


        /// <summary>
        /// Inicializar o provider com as configurações contidas informadas
        /// </summary>
        /// <param name="configuration"></param>
        public FileSystemRepository(ProviderConfigurationElement configuration)
            : base(configuration)
        {
            try
            {
                if (this.Configuration.Settings["directoryPath"] == null)
                    throw new ConfigurationErrorsException("The configuration key 'directoryPath' not found in the config file.");
                else
                {
                    _contingencyDirectoryPath = this.Configuration.Settings["directoryPath"].Value;
                    if (string.IsNullOrEmpty(_contingencyDirectoryPath))
                        throw new ConfigurationErrorsException("The configuration key 'directoryPath' is empty in the config file.");

                    if (!Directory.Exists(_contingencyDirectoryPath))
                        Directory.CreateDirectory(_contingencyDirectoryPath);
                }
            }
            catch (Exception ex)
            {
                throw new UnavailableRepositoryException(configuration.Name, ex);
            }
        }


        /// <summary>
        /// Adicionar um item na pilha de contingency
        /// </summary>
        /// <param name="entryPackage"></param>
        public void Send(LogEntryPackage entryPackage)
        {
            try
            {
                lock (_syncRoot)
                {
                    string fileName = Path.Combine(_contingencyDirectoryPath, DateTime.Now.ToString(_fileNamePattern) + _fileExtension);
                    //garantir q n havera conflito de nome de arquivos no caso de uma carga grande de log sendo gerada
                    while (File.Exists(fileName))
                    {
                        Thread.Sleep(10);
                        fileName = Path.Combine(_contingencyDirectoryPath, DateTime.Now.ToString(_fileNamePattern) + _fileExtension);
                    }

                    BinarySerializer ser = new BinarySerializer();
                    ser.SerializeToFile(fileName, entryPackage, false);
                }
            }
            catch (Exception ex)
            {
                throw new UnavailableRepositoryException(this.Configuration.Name, ex);
            }
        }

        public LogEntryPackage Receive()
        {
            try
            {
                lock (_syncRoot)
                {
                    BinarySerializer ser = new BinarySerializer();
                    string file = GetFirstFileName();
                    if (!string.IsNullOrEmpty(file))
                    {
                        LogEntryPackage ret = ser.DeserializeFromFile<LogEntryPackage>(file, false);
                        DeleteFile(file);
                        return ret;
                    }
                    else
                        return null;
                }
            }
            catch (SerializationException ex)
            {
                //throw new InvalidLogEntryPackageException("Error retrieving log entry package from the repository. See the inner exception for more details.", ex);
                throw new InvalidLogEntryPackageException(ex);
            }
            catch (Exception ex)
            {
                throw new UnavailableRepositoryException(this.Configuration.Name, ex);
            }
        }

        public LogEntryPackage Peek()
        {
            try
            {
                lock (_syncRoot)
                {
                    BinarySerializer ser = new BinarySerializer();
                    string file = GetFirstFileName();
                    if (!string.IsNullOrEmpty(file))
                    {
                        return ser.DeserializeFromFile<LogEntryPackage>(file, false);
                    }
                    return null;
                }
            }
            catch (SerializationException ex)
            {
                //throw new InvalidLogEntryPackageException("Error retrieving log entry package from the repository. See the inner exception for more details.", ex);
                throw new InvalidLogEntryPackageException(ex);
            }
            catch (Exception ex)
            {
                throw new UnavailableRepositoryException(this.Configuration.Name, ex);
            }
        }

        public void RemoveTop()
        {
            try
            {
                lock (_syncRoot)
                {
                    string file = GetFirstFileName();
                    if (!string.IsNullOrEmpty(file))
                        DeleteFile(file);
                }
            }
            catch (Exception ex)
            {
                throw new UnavailableRepositoryException(this.Configuration.Name, ex);
            }
        }

        /// <summary>
        /// Excluir um arquivo, realizando até 10 tentativas com intervalo de 1 seg
        /// </summary>
        /// <param name="file"></param>
        private void DeleteFile(string file)
        {
            //tentar apagar o arquivo e caso nao consiga tentar novamenete apos 1 seg
            for (int i = 0; i < 10; i++)
            {
                try
                {
                    File.Delete(file);
                    break;
                }
                catch
                {
                    Thread.Sleep(1000);
                }
            }
        }

        private string GetFirstFileName()
        {
            //listar os arquivos do diretorio
            string[] files = Directory.GetFiles(_contingencyDirectoryPath, "*" + _fileExtension);
            if (files != null && files.Length > 0)
            {
                IOrderedEnumerable<string> orderedFiles = files.OrderBy(a => a);
                return orderedFiles.First();
            }
            else
                return string.Empty;
        }

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion
    }
}
